package com.ctrip.corp.obt.shard.annotation;

import com.ctrip.corp.obt.generic.utils.StringUtils;
import com.ctrip.corp.obt.shard.exception.ShardException;
import com.ctrip.corp.obt.shard.handler.IShardingHandler;
import com.ctrip.corp.obt.shard.holder.ShardModelHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author marco.zheng
 * @version 1.0
 * @date 2021/05/06
 * @desc ShardTableAnnotationBeanPostProcessor
 * @see
 * @since 1.0
 */
@Slf4j
public class ShardModelAnnotationBeanPostProcessor implements Ordered, BeanPostProcessor {

	private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));

	private final IShardingHandler shardingHandler;

	public ShardModelAnnotationBeanPostProcessor(IShardingHandler shardingHandler) {
		this.shardingHandler = shardingHandler;
	}

	@Override
	public int getOrder() {
		return Ordered.LOWEST_PRECEDENCE;
	}

	@Override
	public Object postProcessBeforeInitialization(@NonNull Object bean, final String beanName) {
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(final Object bean, final String beanName) {
		if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
			// 判断类上是否有注解
			Class<?> targetClass = AopUtils.getTargetClass(bean);
			// 获取bean的class并判断bean上是否有ShardModel注解
			ShardModel shardModelOnClass = findListenerAnnotation(targetClass);
			final boolean hasClassLevelListeners = Objects.nonNull(shardModelOnClass);
			if (hasClassLevelListeners) {
				handleShardModelOnClass(targetClass, shardModelOnClass);
			} else {
				handleShardModelOnMethod(bean, beanName, targetClass);
			}
		}
		return bean;
	}

	/**
	 * handle ShardModel on method
	 * @param bean
	 * @param beanName
	 * @param targetClass
	 */
	private void handleShardModelOnMethod(Object bean, String beanName, Class<?> targetClass) {
		// 判断方法上是否有ShardModel注解
		Map<Method, Set<ShardModel>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
				(MethodIntrospector.MetadataLookup<Set<ShardModel>>) method -> {
					Set<ShardModel> listenerMethods = findShardModelAnnotations(method);
					return (!listenerMethods.isEmpty() ? listenerMethods : null);
				});
		// 如果扫描到类或者方法上添加了该注释
		if (annotatedMethods.isEmpty()) {
			this.nonAnnotatedClasses.add(bean.getClass());
			if (log.isTraceEnabled()) {
				log.trace("No @ShardModel annotations found on bean type: " + bean.getClass());
			}
		} else {
			// Non-empty set of methods
			for (Map.Entry<Method, Set<ShardModel>> entry : annotatedMethods.entrySet()) {
				Method method = entry.getKey();
				for (ShardModel shardModel : entry.getValue()) {
					// 处理添加了ShardModel注解的方法，并注册Receiver
					processShardModel(shardModel, method);
				}
			}
			if (log.isDebugEnabled()) {
				log.debug(annotatedMethods.size() + " @ShardModel methods processed on bean '"
						+ beanName + "': " + annotatedMethods);
			}
		}
	}

	/**
	 * handle ShardModel on class
	 * @param targetClass
	 * @param shardModelOnClass
	 */
	private void handleShardModelOnClass(Class<?> targetClass, ShardModel shardModelOnClass) {
		Set<Method> methods = MethodIntrospector.selectMethods(targetClass,
				(ReflectionUtils.MethodFilter) method -> true);
		for (Method method : methods) {
			// 处理添加了ShardModel注解的方法，并注册Receiver
			processShardModel(shardModelOnClass, method);
		}
	}

	/*
	 * AnnotationUtils.getRepeatableAnnotations does not look at interfaces
	 */
	private ShardModel findListenerAnnotation(Class<?> clazz) {
		return AnnotationUtils.findAnnotation(clazz, ShardModel.class);
	}

	/*
	 * AnnotationUtils.getRepeatableAnnotations does not look at interfaces
	 */
	private Set<ShardModel> findShardModelAnnotations(Method method) {
		Set<ShardModel> subscribes = new HashSet<>();
		ShardModel ann = AnnotatedElementUtils.findMergedAnnotation(method, ShardModel.class);
		if (ann != null) {
			subscribes.add(ann);
		}
		return subscribes;
	}

	private void processShardModel(ShardModel shardModel, Method method) {
		// ShardModel预检测
		preCheckShardModel(shardModel);
		ShardModelHolder.put(method, shardModel);
	}

	private void preCheckShardModel(ShardModel shardModel) {
		String tableShardStrategy = shardModel.tableShardStrategy();
		if (StringUtils.isNotBlank(tableShardStrategy)) {
			boolean containsStrategy = shardingHandler.getStrategies().contains(tableShardStrategy);
			// 如果不包含strategy，则抛出异常
			if (!containsStrategy) {
				throw new ShardException("Table shard strategy [%s] is invalid, please modify it or implement under com.ctrip.corp.obt.shard.handler.strategy.ITableRouteStrategy", tableShardStrategy);
			}
		}
	}

}